from collections import OrderedDict

import numpy as np


def preprocess(text):
    """
    对文本进行预处理，包括转为小写、在句号后添加空格、分词，并建立词汇表和对应的映射。

    Args:
        text (str): 输入的文本字符串。

    Returns:
        Tuple[np.ndarray, Dict[str, int], List[str]]: 处理后的语料库、词汇表及映射。
    """
    words = text.lower().replace('.', ' .').split()
    unique_words = list(dict.fromkeys(words))
    # 单词转化为单词id
    word_to_id = {word: idx for idx, word in enumerate(unique_words)}
    # 单词id转化为单词
    id_to_word = unique_words

    corpus = np.array([word_to_id[word] for word in words])

    return corpus, word_to_id, id_to_word


def cos_similarity(x, y, eps=1e-8):
    """计算余弦相似度

    :param x: 向量
    :param y: 向量
    :param eps: 用于防止“除数为0”的微小值
    :return: 余弦相似度
    """
    return np.dot(x, y) / (np.linalg.norm(x) * np.linalg.norm(y) + eps)


def most_similar(query, word_to_id, id_to_word, word_matrix, top=5):
    """相似单词的查找

    :param query: 查询词
    :param word_to_id: 从单词到单词ID的字典
    :param id_to_word: 从单词ID到单词的字典
    :param word_matrix: 汇总了单词向量的矩阵，假定保存了与各行对应的单词向量
    :param top: 显示到前几位
    """
    if query not in word_to_id:
        print(f'{query} is not found')
        return

    print(f'\n[query] {query}')
    query_id = word_to_id[query]
    query_vec = word_matrix[query_id]

    similarity = [cos_similarity(word_matrix[i], query_vec) for i in range(len(id_to_word))]
    # argsort 对数组的索引进行了重拍
    similar_words = [(id_to_word[i], similarity[i]) for i in (-1 * np.array(similarity)).argsort() if
                     id_to_word[i] != query]

    # 基于余弦相似度，按降序输出值
    for word, sim in similar_words[:top]:
        print(f' {word}: {sim}')


def create_co_matrix(corpus, vocab_size, window_size=1):
    """生成共现矩阵
        共现矩阵：在关注某个单词的情况下，对它的周围出现了多少次什么单词进行计数，然后再汇总
        共现矩阵的元素表示两个单词同时出现的次数
        基于统计的方法
    :param corpus: 语料库（单词ID列表）
    :param vocab_size: 词汇个数
    :param window_size: 窗口大小（当窗口大小为1时，左右各1个单词为上下文） 将上下文的大小（也就是说周围的单词有多少个）称为窗口大小
    :return: 共现矩阵
    """

    # 首先，用元素为0的二维数组对co_matrix进行初始化，然后针对语料库中的每一个单词，计算它的窗口中包含的单词
    # 同时，检查窗口内的单词是否超出了语料库的左端和右端
    corpus_size = len(corpus)
    co_matrix = np.zeros((vocab_size, vocab_size), dtype=np.int32)

    for idx, word_id in enumerate(corpus):
        for i in range(1, window_size + 1):
            left_idx, right_idx = idx - i, idx + i

            if left_idx >= 0:
                co_matrix[word_id, corpus[left_idx]] += 1

            if right_idx < corpus_size:
                co_matrix[word_id, corpus[right_idx]] += 1

    return co_matrix


def ppmi(C, verbose=False, eps=1e-8):
    '''生成PPMI（正的点互信息）
        点互信息是一种用于衡量两个事件之间关联程度的指标
        考虑单词单词出现的次数 the本身出现的多，所以PMI的得分会降低
        当PMI是负数的时候，将其视为0，这样就可以将单词间的相关性表示为大于等于0的实数
    参数：
        C (ndarray) - 共现矩阵
        可选参数：
            verbose (bool) - 是否输出进展情况，默认False
            eps (float) - 正则化常数，默认1e-8
    返回值：
        M (ndarray) - PPMI矩阵
    '''
    M = np.zeros_like(C, dtype=np.float32)
    N = np.sum(C)
    S = np.sum(C, axis=0)
    total = C.shape[0] * C.shape[1]
    cnt = 0

    # 进行计算
    for i in range(C.shape[0]):
        for j in range(C.shape[1]):
            pmi = np.log2(C[i, j] * N / (S[j] * S[i]) + eps)
            M[i, j] = max(0, pmi)

            if verbose:
                cnt += 1
                if cnt % (total // 100 + 1) == 0:
                    print('%.1f%% done' % (100 * cnt / total), end='\r')

    if verbose:
        print('100.0% done')

    return M

def create_contexts_target(corpus, window_size=1):
    '''生成上下文和目标词

    :param corpus: 语料库（单词ID列表）
    :param window_size: 窗口大小（当窗口大小为1时，左右各1个单词为上下文）
    :return: 包含两个NumPy数组的元组 (contexts, target)
    '''
    # 从语料库中提取目标词
    target = corpus[window_size:-window_size]
    contexts = []

    # 遍历语料库以创建上下文
    for idx in range(window_size, len(corpus) - window_size):
        # 在窗口内循环以收集上下文单词
        cs = [corpus[idx + t] for t in range(-window_size, window_size + 1) if t != 0]
        contexts.append(cs)

    # 将列表转换为NumPy数组并返回
    return np.array(contexts), np.array(target)


def convert_one_hot(corpus, vocab_size):
    '''转换为one-hot表示

    :param corpus: 单词ID列表（一维或二维的NumPy数组）
    :param vocab_size: 词汇个数
    :return: one-hot表示（二维或三维的NumPy数组）
    '''
    N = corpus.shape[0]

    # 这样的实现使用了 NumPy 的 eye 函数，它返回一个对角矩阵，其中对角线上的元素为1，其余为0
    if corpus.ndim == 1:
        one_hot = np.eye(vocab_size)[corpus]

    elif corpus.ndim == 2:
        one_hot = np.eye(vocab_size)[corpus]

    return one_hot



if __name__ == '__main__':
    words_dict = dict.fromkeys(["apple", "banana", "cherry", "apple"], -1)
    print(words_dict)
